
TI_GDT equ 0
RPL0 equ 0
SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0
section .data
put_int_buffer dq 0 ; 定义 8 字节缓冲区，用于数字到字符的转换

[bits 32]
section .text


;--------------- put_int -----------------
; 功能描述：打印一个整数
;-----------------------------------------
global put_int
put_int:
    pushad
    mov ebp, esp
    mov eax, [ebp + 4 * 9] ; call(4) + pushad(8) = 32byte
    mov edx, eax
    mov edi, 7 ; put_int_buffer 中的偏移量
    mov ecx, 8 ; 32/4=8
    mov ebx, put_int_buffer
.16based_4bits:
    and edx, 0x0F ; 获取最低4位
    cmp edx, 9
    jg .isA2F
    add edx, '0' ; 0~9 的 ASCII = 0~9 + 0的ASCII
    jmp .store
.isA2F:
    ; A~F 的 ASCII = A~F - 10 + A
    sub edx, 10
    add edx, 'A'
.store:
    mov [ebx + edi], dl
    dec edi
    shr eax, 4
    mov edx, eax
    loop .16based_4bits

.ready_to_print:
    inc edi ; 此时 edi 为 -1，加 1 使其为 0
.skip_prefix_0:
    cmp edi, 8
    je .full0
.go_on_skip:
    mov cl, [put_int_buffer + edi]
    inc edi
    cmp cl, '0'
    je .skip_prefix_0
    dec edi
    jmp .put_each_num
.full0:
    mov cl, '0'
.put_each_num:
    push ecx
    call put_char
    add esp, 4
    inc edi
    mov cl, [put_int_buffer + edi]
    cmp edi, 8
    jl .put_each_num
    popad
    ret

;--------------- put_str -----------------
; 功能描述：打印一个以 \0 结尾的字符串
;-----------------------------------------
global put_str
put_str:
    push ebx
    push ecx
    xor ecx, ecx
    mov ebx, [esp + 12]
.goon:
    mov cl, [ebx]
    cmp cl, 0
    jz .str_over
    push ecx
    call put_char
    add esp, 4
    inc ebx
    jmp .goon
.str_over:
    pop ecx
    pop ebx
    ret

;--------------- put_char ----------------
; 功能描述：输出一个字符
;-----------------------------------------
global put_char
put_char:
    pushad
    mov ax, SELECTOR_VIDEO
    mov gs, ax

; 获取当前光标位置
    ; 先得到高 8 位
    mov dx, 0x03d4
    mov al, 0x0e
    out dx, al
    mov dx, 0x03d5
    in al, dx
    mov ah, al

    ; 再获取低 8 位
    mov dx, 0x03d4
    mov al, 0x0f
    out dx, al
    mov dx, 0x03d5
    in al, dx

    ; 将光标位置存入 bx
    mov bx, ax
    ; 获取参数（待打印的字符）
    mov ecx, [esp + 36] ; pushad 压入 4 * 8 = 32 字节
                        ; 加上 call 的 4 字节
;判断该字符的类型
    cmp cl, 0xd    ; CR 回车
    jz .is_carriage_return
    cmp cl, 0xa    ; LF 换号
    jz .is_line_feed
    cmp cl, 0x8    ; BS 退格
    jz .is_backspace
    jmp .put_other ; 其它字符

.is_backspace:
    dec bx
    shl bx, 1

    mov byte [gs:bx], 0x20
    inc bx
    mov byte [gs:bx], 0x07
    shr bx, 1
    jmp .set_cursor

.put_other:
    shl bx, 1
    mov byte [gs:bx], cl
    inc bx
    mov byte [gs:bx], 0x07
    shr bx, 1
    inc bx
    cmp bx, 2000   ; 判断是否满屏
    jl .set_cursor ; 不满屏，则正常走
; 若满屏，则换行处理
.is_line_feed:
.is_carriage_return:
    xor dx, dx ; 被除数高 16 位
    mov ax, bx ; 被除数低 16 位
    mov si, 80 ; 除数
    div si
    sub bx, dx ; bx - 余数，相当于取整
.is_carriage_return_end:
    add bx, 80 ; 下一行
    cmp bx, 2000 ; 若未满屏，则进行换操作
.is_line_feed_end:
    jl .set_cursor

;---------------------------
; 屏幕滚动
;---------------------------
.roll_screen:
; 一个屏幕 0~24 行，共 25 行，现在屏幕向上滚动一行
; 即要将 1~24 行换到 0~23 行
    cld
    mov ecx, 960 ; 2000 - 80 = 1920, 共 1920*2=3840字节
                 ; 一次搬 4 字节，共 3840/4=960 次
    mov esi, 0xc00b80a0 ; 第 1 行，行首，源地址
    mov edi, 0xc00b8000 ; 第 0 行，行首，目的地址
    rep movsd

; 清空最后一行
    mov ebx, 3840 ; 最后一行的行首 = 1920 * 2
    mov ecx, 80
.cls:
    mov word [gs:ebx], 0x0720 ; 0x0720 表示黑底白字的空格键
    add ebx, 2
    loop .cls
    mov bx, 1920 ; 将光标重置为 1920，最后一行的首字符

;---------------------------
; 设置光标，将光标设置为 bx
;---------------------------
.set_cursor:
    ; 先设置高 8 位
    mov dx, 0x03d4
    mov al, 0x0e
    out dx, al
    mov dx, 0x03d5
    mov al, bh
    out dx, al

    ; 再设置低 8 位
    mov dx, 0x03d4
    mov al, 0x0f
    out dx, al
    mov dx, 0x03d5
    mov al, bl
    out dx, al
.put_char_done:
    popad
    ret
